# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import http, api
from odoo.http import request

import logging
_logger = logging.getLogger(__name__)

Operators = {
    'eq':  '=',   # 默认为 '='  if value is integer
    'neq': '!=',
    'gt':  '>',
    'ge':  '>=',
    'lt':  '<',
    'le':  '<=',
    'eq_null':   '=?',
    'like':      'like',
    'not_like':  'not like',
    'in':        'in',         # 默认为 'in' if value is list
    'not_in':    'not in',
    'child_of':  'child_of',
    'parent_of': 'parent_of'
}


class Model2(object):
    """
    TBD: 2019-7-20, obj 需要 with_context(context)

    所有的 fields 参数格式为嵌套的 {{},{}}。同时支持简单的[list]，

    2019-11-27
    简单的 domain, 都是 与 关系的, 可以用 dict 传参数, 前端 使用 dict 这种结构易理解,
    复杂的 domain, 有 或 非 关系的, dict 不容易设计, 依然可以用 list 格式， 

    {
        id:          [int],        # 该格式约定, 等同于 [id, 'in', [int] ]，         常用于 ids 过滤
        partner_id:  int,          # 该格式约定, 等同于 [partner_id, '=', int ]，    常用于 id 过滤
        name___like: 'string',     # 该格式约定, 等同于 [name, 'like', string ]，    常用于 模糊查询
        date___gt:   'yyyy-mm-dd', # 该格式约定, 等同于 [date, '>',  'yyyy-mm-dd' ]，常用于 时间范围查询
        date___ge:   'yyyy-mm-dd', # 该格式约定, 等同于 [date, '>=', 'yyyy-mm-dd' ]，常用于 时间范围查询
        date___lt:   'yyyy-mm-dd', # 该格式约定, 等同于 [date, '<',  'yyyy-mm-dd' ]，常用于 时间范围查询
        date___le:   'yyyy-mm-dd', # 该格式约定, 等同于 [date, '<=', 'yyyy-mm-dd' ]，常用于 时间范围查询
    }
    
    """

    @classmethod
    def _domain_dict2list(cls, domain, domain2 ):
        # domain, domain2 两个参数, 分别传 list 和 dict 参数

        if not domain:
            domain = []

        if not domain2:
            domain2 = []

        def fn(field_op, value):
            field_op_list = field_op.split('___')
            if len(field_op_list) == 1:
                field_op_list = field_op_list + [ isinstance( value, list ) and 'in' or 'eq' ]
            
            field, op = field_op_list
            op = Operators[op]
            return field, op, value

        if isinstance(domain, dict):
            domain = [ fn( field_op, value ) for field_op, value in domain.items() ]

        elif not isinstance(domain, list):
            # raise error
            domain = []

        if isinstance(domain2, dict):
            domain2 = [ fn( field_op, value ) for field_op, value in domain2.items() ]

        elif not isinstance(domain2, list):
            # raise error
            domain2 = []

        return domain + domain2

    @classmethod
    def fields_get(cls, obj, fields=None, *args, **kwargs ):
        # fields is dict, return all models
        # TBD
        return {}

    @classmethod
    def _get_meta(cls, obj, field, metadata=None,):
        
        fs = field.split('.')
        if len(fs) == 1:
            return metadata[field]

        elif len(fs) == 2:
            meta = metadata[fs[0]]
            if meta['type'] in ('many2one','one2many','many2many'):
                ref_metadata = obj.env[meta['relation']].fields_get([fs[1]])
                return ref_metadata[fs[1]]

        return {'type': None}

    @classmethod
    def _get_option(cls, obj, field, metadata=None,
                    domain=None, domain2=None, 
                    fields=None, order=None, cascader=None ):

        if not metadata:
            metadata = {}

        meta = cls._get_meta(obj, field,metadata )

        if not meta:
            return []

        if meta['type'] == 'selection':
            return [ {'value': sel[0], 'name': sel[1]}  for sel in meta['selection'] ]
        
        elif meta['type'] in ['many2one','many2many']:
            ref = obj.env[meta['relation']]
            fields = fields or ['display_name']
            #if isinstance( fields, dict):
            #    fields = list(fields.keys())

            records = cls.search_read(ref, domain=domain, domain2=domain2, fields=fields, order=order)

            if not cascader:
                return records
            
            # TBD 2019-11-21, 根据 cascader 进行递归 处理
            # cascader: {
            #     value: 'id',
            #     label: 'display_name',
            #     children: {
            #         name: 'child_ids',
            #         cascader: {
            #             value: 'id',
            #             label: 'display_name'
            #         }
            #     }
            # }

            return records

        return []

    @classmethod
    def get_options(cls, obj, fields ):
        options = {}
        fs = [ fld.split('.')[0]  for fld in fields ]
        metadata = obj.fields_get(fs)

        for fld, value in fields.items():
            if not value:
                value = {}

            options[fld] = cls._get_option(obj, fld, metadata, **value)

        return options


    @classmethod
    def search_count(cls,obj, domain=None, domain2=None, context=None, *args, **kwargs):
        domain = cls._domain_dict2list( domain, domain2 )
        return obj.search_count( domain )

    @classmethod
    def search_read(cls,obj, domain=None,  domain2=None, fields=None,
                         offset=0, limit=None, order=None, context=None, *args, **kwargs):

        domain = cls._domain_dict2list( domain, domain2 )
        recs = obj.search(domain, offset=offset, limit=limit, order=order)
        return cls._return_for_read(recs,fields, context)

    @classmethod
    def read(cls,obj,ids,fields=None, context=None, *args, **kwargs):
        recs = obj.browse(ids)
        return cls._return_for_read(recs,fields, context)
    
    # 2019-11-27
    # for selection field eg. 'state',      we set a new field 'state__name'
    # for many2one  field eg. 'partner_id', we set a new field 'partner_id__name'
    # for many2one  field eg. 'partner_id', we set a new field 'partner_id__onject' = {id, name, ...}
    # for many2one  field eg. 'partner_id', we set this  field 'partner_id' = id
    # for o2m m2m   field eg. 'line_ids',   we set a new field 'line_ids__names' = 'name1,name2,...'
    # for o2m m2m   field eg. 'line_ids',   we set a new field 'line_ids__objects' = [{id,name}]
    # for o2m m2m   field eg. 'line_ids',   we set this  field 'line_ids' = [ids]

    # TBD, 读取 o2m 字段 嵌套 有 m2o 字段 会出现无限循环
    # 2019-12-23
    @classmethod
    def _return_for_read(cls, obj, fields, context=None):

        if not fields:
            fields = {}

        if fields and isinstance(fields, list):
            fields = dict( (f, None) for f in fields )

        # fn2 be copy from odoo.models.search_read(), 2018-8-13
        def fn2(records, fields11):
            result = records.read(fields11)
            if len(result) <= 1:
                return result

            # reorder read
            index = {vals['id']: vals for vals in result}
            return [index[record.id] for record in records if record.id in index]

        records = fn2(obj, fields.keys())

        metadata = obj.fields_get(fields.keys())

        # TBD, 2019-12-25, fields 为空时 , 回所有字段 而且是 不处理的.

        for rec in records:
            for field, ref_fields in fields.items():
                meta = metadata[field]
                if not meta:
                    continue

                if meta['type'] == 'selection':
                    sel_val = rec[field] or None
                    rec[field] = sel_val

                    if sel_val:
                        rec[ field + '__name' ] = dict( meta['selection'] ).get(sel_val, sel_val)
                    else:
                        rec[ field + '__name' ] = sel_val

                elif meta['type'] in ('many2one','one2many', 'many2many' ) :
                    ref_val = rec[field]
                    if  meta['type'] == 'many2one':
                        m2o_id, m2o_name = ref_val or [ None, '']
                        rec[field] = m2o_id
                        rec[ field + '__name' ] = m2o_name
                        rec[ field + '__object' ] = ref_val and {
                            'id': m2o_id, 
                            'name': m2o_name,
                            'display_name': m2o_name } or {}

                        if ref_val and ref_fields:
                            obj_one = obj.filtered(lambda r: r.id == rec['id'] )
                            ref_obj = getattr(obj_one, field)
                            ref_m2o = cls._return_for_read(ref_obj, ref_fields)
                            ref_m2o = ref_m2o and ref_m2o[0] or {}
                            rec[ field + '__object' ].update(ref_m2o) 

                    else:
                        m2m_ids = ref_val or []
                        rec[ field + '__count' ] = len( m2m_ids )
                        if ref_fields:
                            ref_fields['display_name'] = None
                            obj_one = obj.filtered(lambda r: r.id == rec['id'] )
                            ref_obj = getattr(obj_one, field)
                            ref_records = cls._return_for_read(ref_obj, ref_fields)
                            rec[ field + '__objects' ] = ref_records
                            rec[ field + '__names' ] = ','.join([ m2m['display_name'] for m2m in ref_records] )

                        else:
                            rec[ field + '__names' ] = ', '.join([ '(%s,%d)' % (meta['relation'], rid ) for rid in  m2m_ids] )
                            rec[ field + '__objects' ] = [ {'id': m2m_id} for m2m_id in m2m_ids ]
                
                elif meta['type'] in ['char','text','html','date','datetime']:
                    sel_val = rec[field]
                    rec[field] = sel_val or None 


        return records
  

class JsonApi(http.Controller):
    @http.route('/api2/test1',type='json', auth='none',cors='*',csrf=False)
    def test1(self,**kw):
        print('hello')
        return "hello!"

    @http.route('/api2/api',type='json', auth='user',cors='*',csrf=False)
    def json_api2(self,model,method, args,kwargs,sudo=None):
        _logger.info('model %s call %s with %s, %s, %s', model,method, args,kwargs,sudo)

        odoo_model = request.env[model]
        if sudo:
            odoo_model = odoo_model.sudo(sudo)

        #print('1234567890,odoo_model._onchange_methods', odoo_model._onchange_methods )

        # fields_get2, return multi model
        # search_count2 and search_read2, domain is dict
        # search_read2, read2, fields is dict
        # 2019-11-27 TBD,   getOption only for m2o? 

        if method in ['fields_get2', 'search_count2', 'search_read2', 'read2', 'get_options' ]:
            ret = {
                'fields_get2':   Model2.fields_get,
                'search_count2': Model2.search_count,
                'search_read2':  Model2.search_read,
                'read2':         Model2.read,
                'get_options':   Model2.get_options,
            }[method](odoo_model, *args, **kwargs)

            return ret

        if method == 'create2':
            method = 'create'
        elif method == 'write2':
            method = 'write'

        return api.call_kw(odoo_model, method, args, kwargs)


    @http.route('/api2/user/logout',type='json', auth='user', cors='*', csrf=False )
    def destroy(self):
        request.session.logout()


    @http.route('/api2/user/login',type='json', auth='none', cors='*', csrf=False )
    def login(self,db,login,password, **kw):
        _logger.info('call login with %s, %s, %s', db,login,password )
        
        request.session.authenticate(db, login, password)
        request.session.rotate = False

        session_info = request.env['ir.http'].session_info()
        
        # for odoo 13, session_id is not in session_info
        if not session_info.get('session_id'):
            session_info['session_id'] = request.session.sid
        return session_info



